/******************************************************************************

Welcome to GDB Online.
GDB online is an online compiler and debugger tool for C, C++, Python, PHP, Ruby, 
C#, VB, Perl, Swift, Prolog, Javascript, Pascal, HTML, CSS, JS
Code, Compile, Run and Debug online from anywhere in world.

*******************************************************************************/
#include <stdio.h>
#include <iostream>

// 可变参模板  【Variabli Template】c++11引入
// 允许模板定义中包含 0 到 任意多个 模板参数
// c++17中又引入了可变参模板的很多新特性

// 可变参函数模板
// 含义
// sizeof... c++11引入 的 一种固定语法
// 一般就用在可变参函数模板或者可变参类模板中，用于统计可变参的数量（收到的模板参数数量或者类型数量）
// sizeof... 针对的只能是这种...的可变参， 后面的圆括号里可以是函数模板的形参args，也可以是类型T

// 参数包展开
// 可变参函数模板中，传递进来的一包实参怎么展开
// 一般都采用递归方式展开参数包
// 这种递归方式，一般都要求在可变参函数模板代码中，有一个参数包展开函数 和一个递归终止函数

// c++17中新增了一种语句，叫做编译期间if语句 (constexpr if)
// 实际代码书写格式
// if constexpr() {...}
// constexpr 代表常量（或者编译期间求值）的意思，如果编译期间不能求值，就会报语法错
// 使用这个 编译期间if，就能把可变参模板的递归终止函数省略不写

// 深入认识 if constexpr
// 1）不满足条件的分支，也同样会被编译器编译（或者说：被编译器进行语法检查）
// 2）if constexpr所指定的条件必须是常量
// 3）你可以将这个if语句理解成普通的if语句，只是这个判断条件从执行期间挪到了编译期间
// 4）那既然这样，就要求这个 if constexpr所判断的语句，必须是编译期间可以确认的值（编译期间可确定的常量表达式）
// 比如这种语句，就不行
// int i = 10;
// if constexpr (i > 5) {}
// 因为i是一个变量，只有在代码执行起来，i的值才能确认，换句话说就是i的值只有在运行期间才能知道
// 不属于编译期间能知道的，使用if constexpr就会报错
// if constexpr(19 > 14){}
// 这个就可以，因为编译期间就能判断19 > 14

// 总结：if constexpr的存在，完善了模板与泛型编程中程序代码执行路径的选择问题


// 重载
// 一般来说，调用普通函数和调用函数模板都合适的时候，编译器会优先选择普通函数


namespace _nmsp1
{
    
    void funcPT()
    {
        
    }
    
    // 可变参函数模板
    template<typename... T> // ...代表参数包， T称为可变参数类型，T中包含0到多个不同的类型（一包类型）
    void variFunc(T... args)    // T 一包类型，args一包形参，每个参数的类型可以各不相同，这包参数就是函数模板形参
    {
        std::cout << "-----------------begin------------------" << std::endl;
        
        std::cout << "收到的类型数量 = " << sizeof...(T) << std::endl;
        
        std::cout << "收到的参数数量 = " << sizeof...(args) << std::endl;
        
        std::cout << "------------------endl------------------" << std::endl;
        
        // sizeof... c++11引入 的 一种固定语法
        // 一般就用在可变参函数模板或者可变参类模板中，用于统计可变参的数量（收到的模板参数数量或者类型数量）
        // sizeof... 针对的只能是这种...的可变参， 后面的圆括号里可以是函数模板的形参args，也可以是类型T
    }
    
    void func()
    {
        variFunc();
        // -----------------begin------------------
        // 收到的类型数量 = 0
        // 收到的参数数量 = 0
        // ------------------endl------------------
        
        variFunc(10, 324);
        // -----------------begin------------------
        // 收到的类型数量 = 2
        // 收到的参数数量 = 2
        // ------------------endl------------------
        
        variFunc("string", 'C', 23.43, 34.33f, 342, "string2", 352);
        // -----------------begin------------------
        // 收到的类型数量 = 7
        // 收到��参数数量 = 7
        // ------------------endl------------------
        
        variFunc<std::string, int>("string", 'c', 34.3f, 23);
        // 指定部分类型，让编译器去推断另一部分的类型，是允许的
        // （注意哪怕前面给定的参数类型不是紧挨着的判断顺序，也是可以的）
        // -----------------begin------------------
        // 收到的类型数量 = 4
        // 收到的参数数量 = 4
        // ------------------endl------------------
        
    }
}

namespace _nmsp2
{
    // 同名的递归终止函数（不是函数模板，是一个真正的函数）
    // 注意这个函数不能放在参数包展开函数的后面，必须放在前面，放后面会爆语法错
    void funcVar()  // 这是一个普通函数，不是函数模板
    {
        std::cout << "同名的递归终止函数被掉用" << std::endl;
    }
    
    
    // 参数包展开函数
    template<typename T, typename... U>
    void funcVar(T firstArg, U... otherArgs)
    {
        std::cout << "-----------------begin------------------" << std::endl;
        std::cout << "收到的参数值： " << firstArg << std::endl;
        std::cout << "剩余参数包大小：" << sizeof...(otherArgs) << std::endl;
        std::cout << "------------------endl------------------" << std::endl;
        
        // 然后递归调用
        // 注意这里塞进来的是一包形参，这里的...不能省略
        funcVar(otherArgs...);
    }
    
    
    
    
    void func()
    {
        funcVar("string", 'C', 23.43, 34.33f, 342, "string2", 352);
        // funcVar("string", 'C', 23.43, 34.33f, 342, "string2", 352); 第一次递归调用
        // -----------------begin------------------
        // 收到的参数值： string
        // 剩余参数包大小：6
        // ------------------endl------------------
        // funcVar('C', 23.43, 34.33f, 342, "string2", 352); 第二次递归调用
        // -----------------begin------------------
        // 收到的参数值： C
        // 剩余参数包大小：5
        // ------------------endl------------------
        // funcVar(23.43, 34.33f, 342, "string2", 352); 第三次递归调用
        // -----------------begin------------------
        // 收到的参数值： 23.43
        // 剩余参数包大小：4
        // ------------------endl------------------
        // funcVar(34.33f, 342, "string2", 352); 第四次递归调用
        // -----------------begin------------------
        // 收到的参数值： 34.33
        // 剩余参数包大小：3
        // ------------------endl------------------
        // funcVar(342, "string2", 352); 第五次递归调用
        // -----------------begin------------------
        // 收到的参数值： 342
        // 剩余参数包大小：2
        // ------------------endl------------------
        // funcVar("string2", 352); 第六次递归调用
        // -----------------begin------------------
        // 收到的参数值： string2
        // 剩余参数包大小：1
        // ------------------endl------------------
        // funcVar(352); 第七次递归调用
        // -----------------begin------------------
        // 收到的参数值： 352
        // 剩余参数包大小：0
        // ------------------endl------------------
        // 跳出递归（参数为空，执行同名的递归终止函数 funcVar() ）这也就是为什么终止函数里参数为空的原因
        // 同名的递归终止函数被掉用
    }
}

namespace _nmsp3
{

    // 参数包展开函数
    template<typename T, typename... U>
    void funcVar(T firstArg, U... otherArgs)
    {
        std::cout << "-----------------begin------------------" << std::endl;
        std::cout << "收到的参数值： " << firstArg << std::endl;
        std::cout << "剩余参数包大小：" << sizeof...(otherArgs) << std::endl;
        std::cout << "------------------endl------------------" << std::endl;
        
        // 注意：constexpr 在这里必须有，否者就是运行时求值，就会无法成功编译
        // 同时()里的是常量表达式，编译的时候就必须得能够求出值来
        // 因为对于这个参数包展开函数，递归会在编译的时候就被展开，所以编译的时候，这些参数值就都是已知的了
        if constexpr (sizeof...(otherArgs) > 0)
        {
            // 然后递归调用
            // 注意这里塞进来的是一包形参，这里的...不能省略
            funcVar(otherArgs...);
        }
        else
        {
            std::cout << "参数遍历完毕！" << std::endl;
        }
    }
    
    
    
    
    void func()
    {
        funcVar("string", 'C', 23.43, 34.33f, 342, "string2", 352);
        // funcVar("string", 'C', 23.43, 34.33f, 342, "string2", 352); 第一次递归调用
        // -----------------begin------------------
        // 收到的参数值： string
        // 剩余参数包大小：6
        // ------------------endl------------------
        // funcVar('C', 23.43, 34.33f, 342, "string2", 352); 第二次递归调用
        // -----------------begin------------------
        // 收到的参数值： C
        // 剩余参数包大小：5
        // ------------------endl------------------
        // funcVar(23.43, 34.33f, 342, "string2", 352); 第三次递归调用
        // -----------------begin------------------
        // 收到的参数值： 23.43
        // 剩余参数包大小：4
        // ------------------endl------------------
        // funcVar(34.33f, 342, "string2", 352); 第四次递归调用
        // -----------------begin------------------
        // 收到的参数值： 34.33
        // 剩余参数包大小：3
        // ------------------endl------------------
        // funcVar(342, "string2", 352); 第五次递归调用
        // -----------------begin------------------
        // 收到的参数值： 342
        // 剩余参数包大小：2
        // ------------------endl------------------
        // funcVar("string2", 352); 第六次递归调用
        // -----------------begin------------------
        // 收到的参数值： string2
        // 剩余参数包大小：1
        // ------------------endl------------------
        // funcVar(352); 第七次递归调用
        // -----------------begin------------------
        // 收到的参数值： 352
        // 剩余参数包大小：0
        // ------------------endl------------------
        // 参数遍历完毕！
    }
}

namespace _nmsp4
{
    template<typename... T>
    void myFunc(T... args)
    {
        std::cout << "myFunc(T... args)执行了" << std::endl;
    }
    
    template<typename... T>
    void myFunc(T*... args)
    {
        std::cout << "myFunc(T*... args)执行了" << std::endl;
    }
    
    void myFunc(int arg)
    {
        std::cout << "myFunc(int arg)执行了" << std::endl;
    }
    
    void func()
    {
        // 其实从这里也可以看出来，NULL和0虽然值相等，但是他们的类型却不是一样的
        
        myFunc(0);
        // myFunc(int arg)执行了
        myFunc(NULL);       // 空 0 来掉用
        // myFunc(T... args)执行了
        myFunc(nullptr);    // 空指针（不是0）来调用
        // myFunc(T... args)执行了
        myFunc((int *)nullptr); // 空指针硬转int*指针调用
        // myFunc(T*... args)执行了
    }
}

int main()
{
    // _nmsp1::func();
    // _nmsp2::func();
    // _nmsp3::func();
    _nmsp4::func();
    
    if constexpr (NULL == 0)
    {
        std::cout << "NULL == 0" << std::endl;
        std::cout << typeid(NULL).name() << std::endl;
        std::cout << typeid(0).name() << std::endl;
    }
    // NULL == 0


    return 0;
}

